package com.smsc.headend.connector.service.protocol.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.SystemClock;
import cn.hutool.core.lang.Tuple;
import cn.hutool.core.util.HexUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.google.common.base.Preconditions;
import com.google.common.primitives.Bytes;
import com.smsc.headend.common.enums.DeviceType;
import com.smsc.headend.common.utils.CosemUtils;
import com.smsc.headend.common.utils.RedisKeys;
import com.smsc.headend.common.utils.RedisUtils;
import com.smsc.headend.connector.bo.*;
import com.smsc.headend.connector.builder.LoggerBuilder;
import com.smsc.headend.connector.exception.UDISDeviceConException;
import com.smsc.headend.connector.manager.AssociationManager;
import com.smsc.headend.connector.netty.NettyConfigHelper;
import com.smsc.headend.connector.service.*;
import com.smsc.headend.connector.service.asset.AssetService;
import com.smsc.headend.connector.service.impl.DeviceConnectServiceImpl;
import com.smsc.headend.connector.service.protocol.DLMSPacketCodecService;
import com.smsc.headend.connector.utils.*;
import com.smsc.headend.module.asset.dmo.ComConfigDMO;
import com.smsc.headend.module.asset.dmo.DLMSConfigDMO;
import com.smsc.headend.module.asset.entity.ComEntity;
import com.smsc.headend.module.asset.entity.Meter;
import com.smsc.headend.module.protocol.ProtocolType;
import com.smsc.headend.module.protocol.cosem.bo.CosemData;
import com.smsc.headend.module.protocol.cosem.util.ByteNumberUtils;
import com.smsc.headend.module.protocol.cosem.util.ByteUtils;
import com.smsc.headend.module.protocol.cosem.util.CosemXmlUtils;
import com.smsc.headend.module.task.dto.ConnectorTask;
import com.smsc.headend.module.task.dto.ConnectorTaskResult;
import gurux.dlms.*;
import gurux.dlms.enums.InterfaceType;
import gurux.dlms.objects.enums.SecuritySuite;
import gurux.dlms.secure.AesGcmParameter;
import gurux.dlms.secure.GXCiphering;
import gurux.dlms.secure.GXDLMSSecureClient;
import io.netty.channel.Channel;
import io.netty.util.Attribute;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.io.UnsupportedEncodingException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static com.smsc.headend.connector.utils.CosemXMLUtil.*;

@Service
@Slf4j
public class DLMSPacketCodecServiceImpl implements DLMSPacketCodecService {
    public static final String GURUX_NONE_DATA_STRING = "<None />";
    //威胜GPRS模块推送的心跳报文头，不需要回复心跳帧
    public static final String DHB = "DHB;";
    //威胜GPRS模块推送的登录报文头，登陆后仍有可能发送，作心跳处理
    public static final String DRLI = "DRLI;";
    public static final String DATA_NOTIFICATION = "DataNotification";
    @Autowired
    public TerminalChannelService terminalChannelService;
    @Autowired
    DeviceConnectService deviceConnectService;
    @Autowired
    FeignAssetManagementService assetManagementRemoteService;
    @Autowired
    LogTerminalAccessService logTerminalAccessService;
    @Autowired
    AssociationManager associationManager;
    @Autowired
    TaskService taskService;
    @Autowired
    LogFileNameUtil logFileNameUtil;
    @Autowired
    RedisTemplate redisTemplate;
    @Autowired
    @Qualifier("savePacketPool")
    ThreadPoolExecutor savePool;
    @Autowired
    RedisUtils redisUtils;
    @Autowired
    @Qualifier("connectorTaskPool")
    ThreadPoolExecutor connectorTaskPool;
    @Autowired
    DeviceStatusService deviceStatusService;
    @Autowired
    AssetService assetService;


    ThreadLocal<String> cosemXml = new ThreadLocal<>();

    ThreadLocal<String> pduXml = new ThreadLocal<>();

    static final byte[] DLMS_GATEWAY_HEARTBEAT = new byte[]{
            0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00
    };

    static final byte[] GPRS_HDLC_HEARTBEAT = new byte[]{
            0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00
    };

    static final int[] glo_tag = new int[]{
            33, 37, 38, 40, 44, 45, 46, 200, 201, 203, 204, 205, 207
    };

    public static final byte[] wrapper = new byte[]{0x00, 0x01, 0x00, 0x01, 0x00, 0x01};

    //初始化TaskContext时，需要注意并发场景
    ReentrantLock createContextLock = new ReentrantLock();


    @Override
    public void sendPacket(ConnectorTask connectorTask, Channel channel) {
        byte[] businessPacket = this.convertConnectorTask(connectorTask, channel);
        if (businessPacket != null) {
            deviceConnectService.unlockComEntity(channel);
            channel.writeAndFlush(businessPacket);
        }
    }

    public void configRetryBO(boolean noRetry, Channel channel, byte[] businessPacket, ConnectorTask connectorTask, String deviceAddress) {
        channel.attr(NettyConfigHelper.resendPkg).set(new RetryBo(SystemClock.now(), businessPacket, connectorTask.getAtomicTaskNo(), (short) 0, deviceAddress, connectorTask.getFlag(), !noRetry));
    }

    /**
     * 判断connector task类型， 确定是否要建立Association，并在有需要情况下对pdu进行加密
     *
     * @param connectorTask
     * @param channel
     * @return
     */
    @Override
    public byte[] convertConnectorTask(ConnectorTask connectorTask, Channel channel) {
        ComEntity terminal = channel.attr(NettyConfigHelper.comEntityAttrKey).get();
        Meter meter = null;
        if (DeviceType.GPRS.getId().intValue() != terminal.getComDeviceType() && connectorTask.getMeterId() != null) {
            //Meter under terminal
            meter = assetService.getMeterById(connectorTask.getMeterId());
        }
        String deviceAddress = meter == null ? "-1" : meter.getMfgSn();
        boolean is46Meter = false;
        if (DeviceType.GPRS.getId().intValue() == terminal.getComDeviceType() && connectorTask.getMeterId() != null) {
            meter = assetService.getMeterById(connectorTask.getMeterId());
            DLMSConfigDMO dlmsConfigDMO = this.getDLMSConfigDMO(terminal, meter);
            if (dlmsConfigDMO != null && dlmsConfigDMO.getInterfaceType().equalsIgnoreCase(InterfaceType.HDLC.name())) {
                is46Meter = true;
                // 46 HDLC子母表信息地址
                deviceAddress = Convert.toStr(GxAddrUtil.getHDLCServerAddress(GxAddrUtil.setDesId(meter.getDeviceAddr())));
            }
        }

        String netAddress = NetUtil.getIpFromChannel(channel);
        //将报文设置到channel attribute
        channel.attr(NettyConfigHelper.packetKey).set(connectorTask.getPacketBytes());
        channel.attr(NettyConfigHelper.connectTaskRegular).set(connectorTask);

        if ((connectorTask.getFlag() & 2) == 2 && connectorTask.getPacketBytes() != null) {
            LoggerBuilder.getLogger(logFileNameUtil.getLogFileName(terminal), this.getClass()).debug("send direct packet {}", connectorTask.getAtomicTaskNo());
            // 46表计暂时不重试
//            deviceConnectService.sendPacket2Channel(channel, connectorTask.getPacketBytes()[0], !is46Meter, null, null);
            ConnectionInfo reqConnectInfo = new ConnectionInfo();
            reqConnectInfo.setComId(terminal.getComId()).setTaskNo(connectorTask.getTaskNo()).setReturnPacket(connectorTask.getReturnPacket()).setAtomicTaskNo(connectorTask.getAtomicTaskNo());
            if (meter != null) {
                reqConnectInfo.setMeterId(meter.getMeterId());
            }
            if (connectorTask.getReturnPacket()) {
                logTerminalAccessService.savePacket(reqConnectInfo, SystemClock.now(), connectorTask.getPacketBytes()[0], Boolean.TRUE);
            }
//            if ((connectorTask.getFlag() & 4) == 4) {
//                deviceConnectService.sendDeviceResultMessage(new ConnectTaskResultDto(ConnectorTaskResult.ExecuteCode.success, connectorTask.getAtomicTaskNo(), null, "sent", connectorTask.getTerminalId(), connectorTask.getFlag(), connectorTask.getTicketNumber()), terminal);
//            }
            //直接发送的报文，不需要走下面的预处理逻辑
            return connectorTask.getPacketBytes()[0];
        }

        AssociationState aaState = associationManager.getApplicationAssociationState(netAddress, deviceAddress, connectorTask.getRoleId());
        if (associationManager.needRebuildAssociation(aaState, meter, terminal, connectorTask, netAddress)) {
            try {
                byte[] packetToSend = associationManager.getAssociationFirstPacket(netAddress, deviceAddress, terminal, meter, connectorTask);
                configRetryBO(is46Meter, channel, packetToSend, connectorTask, deviceAddress);
                return packetToSend;
            } catch (UnsupportedEncodingException | UDISDeviceConException e) {
                deviceConnectService.sendDeviceResultMessage(new ConnectTaskResultDto(ConnectorTaskResult.ExecuteCode.fail, connectorTask.getAtomicTaskNo(), null, "build aa fail", connectorTask.getTerminalId(), connectorTask.getFlag(), connectorTask.getTicketNumber()), terminal);
                return null;
            }
        }
        byte[] businessPacket = this.initialTaskAndSend(terminal, aaState, channel, false);
        if (businessPacket != null) {
            configRetryBO(is46Meter, channel, businessPacket, connectorTask, deviceAddress);
        }
        return businessPacket;
    }

    @Override
    public void buildAAFailedCallBack(ComEntity terminal, Channel channel) {
        ConnectorTask connectorTask = channel.attr(NettyConfigHelper.connectTaskRegular).get();
        deviceConnectService.sendDeviceResultMessage(new ConnectTaskResultDto(ConnectorTaskResult.ExecuteCode.fail, connectorTask.getAtomicTaskNo(), null, "build aa fail", connectorTask.getTerminalId(), connectorTask.getFlag(), connectorTask.getTicketNumber()), terminal);
    }

    public byte[] initialTaskAndSend(ComEntity terminal, AssociationState aaState, Channel channel, boolean isRebuildAa) {
        String deviceAddress = aaState.getDeviceAddress();
        ConnectorTask connectorTask = channel.attr(NettyConfigHelper.connectTaskRegular).get();

        //AA成功之后
        if ((connectorTask.getFlag() & 1) == 1 && connectorTask.getPacketBytes() == null) {
            LoggerBuilder.getLogger(logFileNameUtil.getLogFileName(terminal), this.getClass()).info("AA create forcibly success");
            deviceConnectService.sendDeviceResultMessage(new ConnectTaskResultDto(ConnectorTaskResult.ExecuteCode.success, connectorTask.getAtomicTaskNo(), null, "connected", connectorTask.getTerminalId(), connectorTask.getFlag(), connectorTask.getTicketNumber()), terminal);
            return null;
        }
        String netAddress = NetUtil.getIpFromChannel(channel);
        TaskContext context = getInitialTaskContext(connectorTask, terminal, netAddress, aaState);
        if (context == null) {
            return null;
        }
        byte[] wrappedPacket = new byte[0];
        try {
            wrappedPacket = preSendPacketEncode(netAddress, connectorTask, context, terminal, deviceAddress, aaState, isRebuildAa);
        } catch (UDISDeviceConException e) {
            LoggerBuilder.getLogger(logFileNameUtil.getLogFileName(terminal), this.getClass()).error("invokeId still in use");
            deviceConnectService.sendDeviceResultMessage(new ConnectTaskResultDto(ConnectorTaskResult.ExecuteCode.fail, connectorTask.getAtomicTaskNo(), null, e.getMessage(), connectorTask.getTerminalId(), connectorTask.getFlag(), connectorTask.getTicketNumber()), terminal);
            return null;
        }
        if (wrappedPacket == null) {
            log.error("get packet fail comId={}, taskNo={}, atomicTaskNo={}", terminal.getComId(), connectorTask.getTaskNo(), connectorTask.getAtomicTaskNo());
            deviceConnectService.sendDeviceResultMessage(new ConnectTaskResultDto(ConnectorTaskResult.ExecuteCode.fail, connectorTask.getAtomicTaskNo(), null, "get packet fail", connectorTask.getTerminalId(), connectorTask.getFlag(), connectorTask.getTicketNumber()), terminal);
            return null;
        }
        byte[] lastPacket = new byte[wrappedPacket.length];
        System.arraycopy(wrappedPacket, 0, lastPacket, 0, wrappedPacket.length);
        log.debug("sending packet: context={}", context.toString());
//        deviceConnectService.sendPacket2Channel(channel, wrappedPacket, !is46Meter, connectorTask.getAtomicTaskNo(), deviceAddress);
        //发送后的信息记录
        Long requestTime = SystemClock.now();
        aaState.setLastRequestTime(requestTime);
        terminalChannelService.setChannelLastSendContext(netAddress, context);
        return wrappedPacket;
    }

    public TaskContext getInitialTaskContext(ConnectorTask connectorTask, ComEntity terminal, String netAddress, AssociationState aaState) {
        TaskContext context = terminalChannelService.getTaskContext(netAddress, (aaState.getDeviceAddress() == null || Objects.equals(aaState.getDeviceAddress(), "-1")) ? null : aaState.getDeviceAddress(), terminal.getComId());
        if (context == null) {
            try {
                createContextLock.lock();
                context = terminalChannelService.getTaskContext(netAddress, (aaState.getDeviceAddress() == null || Objects.equals(aaState.getDeviceAddress(), "-1")) ? null : aaState.getDeviceAddress(), terminal.getComId());
                if (context == null) {
                    context = this.buildTaskContext(terminal, netAddress, connectorTask, aaState);
                }
                createContextLock.unlock();
            } catch (UnsupportedEncodingException e) {
                log.error("{} build context fail", connectorTask.getAtomicTaskNo());
                deviceConnectService.sendDeviceResultMessage(new ConnectTaskResultDto(ConnectorTaskResult.ExecuteCode.fail, connectorTask.getAtomicTaskNo(), null, "build taskContext fail", connectorTask.getTerminalId(), connectorTask.getFlag(), connectorTask.getTicketNumber()), terminal);
                return null;
            } finally {
                if (createContextLock.isLocked()) {
                    createContextLock.unlock();
                }
            }
        }
        log.debug("{}", aaState);
        log.debug("{}", context);
        //非重试任务 ， 初始化参数
        this.initializeTaskContext(context, connectorTask, aaState);
        return context;
    }

    private byte[] preSendPacketEncode(String netAddress, ConnectorTask connectorTask, TaskContext context, ComEntity terminal, String deviceAddress, AssociationState aaState, boolean isRebuildAa) throws UDISDeviceConException {
        ThreadLocal<ConnectionInfo> connectionInfo = deviceConnectService.getConnectionInfoThreadLocal();
        connectionInfo.get().setReturnPacket(connectorTask.getReturnPacket());
        byte[][] packetBytes = connectorTask.getPacketBytes();
        final ConnectionInfo fiCon = connectionInfo.get();
        Long now = SystemClock.now();
        boolean needEncrypt = context.getClient() instanceof GXDLMSSecureClient && this.needEncrypt((GXDLMSSecureClient) context.getClient());
        //修改cosem invoke id
        LinkedList<FramePacket> linkedList = new LinkedList<>();
        String logFileName = logFileNameUtil.getConnectInfoLogFileName(fiCon);
        for (byte[] packetByte : packetBytes) {
            try {
                if (aaState != null && aaState.getClient().getInterfaceType().equals(InterfaceType.HDLC)) {
                    // 如果是46 HDLC协议UTE里面报文client address,server address都为1,在这里重新计算出正确的报文
                    byte[] hdlcData = Cosem46ByteUtil.getHdlcData(8, packetByte);
                    short lastFrameId = isRebuildAa ? aaState.getFrameId() : context.getLastFrameId();
                    int frameType = Cosem46ByteUtil.increaseSendSequence(lastFrameId);
                    packetByte = Cosem46ByteUtil.getHdlcFrame(context.getClient(), frameType, new GXByteBuffer(hdlcData));
                }

                Integer invokeId = this.invokeIdAdd(logFileName, packetByte, netAddress, context, connectorTask.getTaskNo());
                if (aaState != null && aaState.getClient().getInterfaceType().equals(InterfaceType.HDLC)) {
                    // 如果是46 HDLC协议因为invokeId变化则需要重新计算data crc
                    Cosem46ByteUtil.replaceDataCrcPacket(packetByte);
                }
                if (fiCon.getReturnPacket()) {
                    // 报文入库
                    byte[] finalPacketByte = packetByte;
                    logTerminalAccessService.savePacket(fiCon, now, finalPacketByte, Boolean.TRUE);
                }
                //加密报文
                if (needEncrypt) {
                    packetByte = this.encryptHLSPacket(packetByte, (GXDLMSSecureClient) context.getClient());
                    ((GXDLMSSecureClient) context.getClient()).getCiphering().setInvocationCounter(((GXDLMSSecureClient) context.getClient()).getCiphering().getInvocationCounter() + 1);
                }
                //添加报文头 DLMS_gateway添加E6+设备地址
                byte[] data = this.wrapPrefixToPacket(packetByte, terminal, deviceAddress);
                linkedList.add(new FramePacket(connectorTask.getAtomicTaskNo(), invokeId, data));
            } catch (Exception e) {
                log.error("invoke add fail", e);
                return null;
            }
        }
        LoggerBuilder.getLogger(logFileNameUtil.getLogFileName(terminal), this.getClass()).debug("add packet number={}", linkedList.size());
        context.addTaskPacketQueue(connectorTask.getAtomicTaskNo(), linkedList);
        FramePacket framePacket = linkedList.poll();
        byte[] wrappedPacket = framePacket.getData();
        context.getInvokeId().set(framePacket.getInvokeId());
        context.addInvokeIdAndAtomicTaskNo(framePacket.getInvokeId(), connectorTask.getAtomicTaskNo());
        log.debug("send to device {}", ByteUtils.byteToHexString(wrappedPacket));
        return wrappedPacket;
    }


    /**
     * 从登录帧获取设备 逻辑地址。  DLMSGateway下的协议是MFG_NO
     * 威盛的GPRS模块上送的时  hex格式的 sim卡号
     *
     * @param result
     * @return
     */
    @Override
    public Tuple getLogicalAddrFromLoggingFrame(byte[] result) {
        String hexResult = HexUtil.decodeHexStr(ByteUtils.byteToHexString(result).replace(" ", ""));
        if (!hexResult.contains("DHB;") && !hexResult.contains("DRLI;")) {
            if (isHeartBeat(result)) {
                return null;
            }
        }
        return Optional.ofNullable(getLogicAddrInCosem(result, hexResult)).orElse(new Tuple(null, null));
    }

    private Tuple getLogicAddrInCosem(byte[] result, String hexResult) {

        if (hexResult.contains("DHB;")) {
            // Nesco GPRS 表 心跳报文
            return new Tuple(hexResult.substring(hexResult.lastIndexOf("DHB;") + 4), null);
        } else if (hexResult.contains("DRLI;")) {
            // Nesco GPRS 表 登录报文
            return new Tuple(hexResult.substring(hexResult.lastIndexOf("DRLI;") + 5), null);
        }
        String cosemPduXml = null;
        try {
            cosemPduXml = translator.pduToXml(CosemXMLUtil.translator.getPdu(result));
        } catch (RuntimeException | InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException | InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) {
            log.error("LoggingFrame cannot be decode in COSEM protocol");
        }

        if (StringUtils.isNotEmpty(cosemPduXml)) {
            CosemData data = CosemXmlUtils.translateCosemXml(cosemPduXml);
            if (data == null) {
                String sanxingDeviceAddr = getSanxingDeviceAddr(cosemPduXml);
                return sanxingDeviceAddr == null ? null : new Tuple(sanxingDeviceAddr, getSanxingSignInResponse(result));
            }
            if (!validateLoggingFrame(data)) {
                log.error("LoggingFrame is not in correct structure");
                return null;
            }
            String logicAddr = getLogicAddr(data);
            return logicAddr == null ? null : new Tuple(logicAddr, getDlmsGatewaySignInResponse());
        } else {
            log.error("LoggingFrame translate COSEM pdu empty");
        }
        return null;
    }

    private byte[] getDlmsGatewaySignInResponse() {
        return DLMS_GATEWAY_HEARTBEAT;
    }

    private boolean validateLoggingFrame(CosemData data) {
        if (!DATA_NOTIFICATION.equalsIgnoreCase(data.getType())) {
            return false;
        }

        if (!data.getChildAt(2).getIsNull() && !data.getChildAt(2).getChildAt(0).getIsNull()) {
            CosemData dataValue = data.getChildAt(2).getChildAt(0);
            if (!"DataValue".equalsIgnoreCase(dataValue.getType())) {
                return false;
            }

            if (dataValue.getChildAt(0).getIsNull()) {
                return false;
            }

            CosemData structure = dataValue.getChildAt(0);
            if (!"Structure".equalsIgnoreCase(structure.getType())) {
                return false;
            }

            if (structure.getChild().stream().filter(o -> !"OctetString".equalsIgnoreCase(o.getType())).findAny().isPresent()) {
                return false;
            }
//            0000190900FF
            Pattern compile = Pattern.compile("[0-9A-F]{12}");
            if (!compile.matcher(structure.getChildAt(0).getValue()).find() || !compile.matcher(structure.getChildAt(1).getValue()).find()) {
                return false;
            }
            return true;
        }
        return false;
    }


    private byte[] getSanxingSignInResponse(byte[] result) {
        byte[] dest = new byte[result.length];
        System.arraycopy(result, 0, dest, 0, dest.length);
        dest[8] = 0x0c;
        dest[9] = 0x03;
        return dest;
    }


    @Override
    public boolean isHeartBeat(byte[] result) {
        String hexResult = HexUtil.decodeHexStr(ByteUtils.byteToHexString(result).replace(" ", ""));
        if (hexResult.contains(DHB) || hexResult.contains(DRLI)) {
            // Nesco GPRS 表 心跳报文
            return true;
        }
        byte[] pduBytes = new byte[0];
        try {
            pduBytes = translator.getPdu(result);
        } catch (Exception e) {
        }
        //cosem heartbeat
        if (pduBytes.length > 0 && pduBytes[0] == 0x01) {
            return true;
        }
        //sanxing device heartbeat
        if (pduBytes.length > 0 && pduBytes[0] == 0x0C) {
            try {
                byte len = pduBytes[2];
                byte[] addrDes = new byte[len];
                System.arraycopy(pduBytes, 3, addrDes, 0, len);
                HexUtil.decodeHexStr(ByteUtils.byteToHexString(addrDes));
                return true;
            } catch (Exception e) {
                log.error("get sanxing heartbeat failed", e);
            }
        }
        return false;
    }

    @Override
    public void heartBeatResp(Channel channel, ComEntity terminal) {
        if (terminal != null && terminal.getComId() > 0) {
            Long hbTime = channel.attr(NettyConfigHelper.hbTime).get();
            if (hbTime == null || SystemClock.now() - hbTime > 10 * 60 * 1000) {
                ComEntity comEntity = terminalChannelService.getTerminalById(terminal.getComId());
                redisUtils.set(RedisKeys.getComLastHeartbeat(comEntity.getComId()), String.valueOf(SystemClock.now() / 1000), DeviceConnectServiceImpl.HEART_BEAT_VALID_SECONDS);
                if (comEntity.getComStatus() == ComEntity.CommunicationStatus.OFFLINE.getCode()) {
                    LoggerBuilder.getLogger(logFileNameUtil.getLogFileName(comEntity), this.getClass()).info("Com heartbeat exists but status offline: {} ", comEntity.getComId());
                    assetManagementRemoteService.updateComEntityStatus(terminal.getComId(), 1, SystemClock.now() / 1000);
                }
                channel.attr(NettyConfigHelper.hbTime).set(SystemClock.now());
            }
        }
    }

    /**
     * only add dlms gateway heartbeat response
     *
     * @param result
     * @return
     */
    public byte[] getResponseBytes(byte[] result) {
        String heartbeatHex = HexUtil.decodeHexStr(ByteUtils.byteToHexString(result).replace(" ", ""));
        if (heartbeatHex.contains(DHB)) {
            return null;
        }

        byte[] pduBytes = new byte[0];
        try {
            pduBytes = translator.getPdu(result);
        } catch (Exception e) {
        }
        //cosem heartbeat
        if (pduBytes.length > 0 && pduBytes[0] == 0x01) {
            if (result[3] == 0x00) {
                return GPRS_HDLC_HEARTBEAT;
            }
            return DLMS_GATEWAY_HEARTBEAT;
        }

        //Sanxing heartbeat
        return getSanxingHeartBeatResp(result);
    }

    private byte[] getSanxingHeartBeatResp(byte[] result) {
        byte[] dest = new byte[result.length];
        System.arraycopy(result, 0, dest, 0, dest.length);
        //控制域
        dest[8] = (byte) (0xcc & 0xff);
        //设备类别  -03代表系统
        dest[9] = 0x03;
        //计算CRC
        int i = GxFcsUtil.countFCS16(dest, 9, dest.length - 11);
        byte[] bytes = ByteNumberUtils.shortToByte2(Convert.toShort(i));
        System.arraycopy(bytes, 0, dest, dest.length - 2, 2);
        return dest;
    }

    public String getLogicAddr(CosemData data) {
        if (!"DataNotification".equalsIgnoreCase(data.getType())) {
            return null;
        }
        CosemData notificationBody = data.getChildAt(2);
        CosemData structure = notificationBody.getChildAt(0).getChildAt(0).getChildAt(2);
        if (structure == null || structure.getIsNull()) {
            return null;
        }
        if ("OctetString".equalsIgnoreCase(structure.getType())) {
            StringBuffer sb = new StringBuffer();
            for (byte b : ByteUtils.decimalStringToByte(structure.getValue())) {
                sb.append((char) Integer.parseInt(String.valueOf(b), 16));
            }
            return sb.toString();
        }
        return structure.getValue();
    }

    private String getSanxingDeviceAddr(String pduStr) {
        Pattern p = Pattern.compile("<Data=\"(.*)\"");
        Matcher matcher = p.matcher(pduStr);

        if (!matcher.find()) {
            return null;
        }
        String group = matcher.group(1);
        byte[] bytes = ByteUtils.hexStringToByte(group);
//        byte sender = bytes[0];
//        byte deviceType = bytes[1];
        byte len = bytes[2];
        byte[] addrDes = new byte[len];
        System.arraycopy(bytes, 3, addrDes, 0, len);
        return HexUtil.decodeHexStr(ByteUtils.byteToHexString(addrDes));
    }


    @Override
    public DevicePacketHandleResult handleDeviceServicePacket(Channel channel, ComEntity terminal, String netAddress, byte[] result, TaskContext taskContext) {
        Logger terminalLogger = LoggerBuilder.getLogger(logFileNameUtil.getLogFileName(terminal), this.getClass());
        UnwrappedPacket unwrappedPacket = this.unwrapPrefixPacket(result);
        //device push data
        DevicePacketHandleResult devicePacketHandleResult = this.handleDevicePullPacket(channel, taskContext, terminal, unwrappedPacket.getPacket(), unwrappedPacket.getDeviceAddress());
        if (Objects.nonNull(devicePacketHandleResult.getConnectTaskResultDto())) {
            if (devicePacketHandleResult.getConnectTaskResultDto().getExecuteCode().getCode() != ConnectorTaskResult.ExecuteCode.ExceptionResponse.getCode()) {
                resetContextAndCleanChannel(netAddress, taskContext, devicePacketHandleResult.getConnectTaskResultDto().getAtomicTaskNo());
            } else {
                //D8 02 03 不释放AA
                //获取release报文
                final AssociationState associationState = associationManager.getRunningDeviceApplicationAssociationState(netAddress, unwrappedPacket.getDeviceAddress());
                Preconditions.checkNotNull(associationState);
                final Integer roleId = associationState.getRoleId();
                final Boolean returnPacket = associationState.getReturnPacket();
                devicePacketHandleResult.setResponse(associationManager.setAssociationInvalid(terminal, netAddress, unwrappedPacket.getDeviceAddress(), true));
                //重建AA并重发报文
                String key = String.format(AssociationManager.CONNECTOR_DEVICE_AA_FAIL, devicePacketHandleResult.getConnectTaskResultDto().getAtomicTaskNo());
                Boolean aaRebuildAndTaskReSendToDevice = redisTemplate.opsForValue().setIfAbsent(key, 1, 5 * 60, TimeUnit.SECONDS);
                terminalLogger.debug("aaRebuildAndTaskReSendToDevice: {}, result={}", aaRebuildAndTaskReSendToDevice, JSONUtil.toJsonStr(devicePacketHandleResult));
                if (aaRebuildAndTaskReSendToDevice) {
                    Channel channelByAddress = NettyConfigHelper.getChannelByAddress(netAddress);
                    ConnectorTask connectorTask = channelByAddress.attr(NettyConfigHelper.connectTaskRegular).get();
                    terminalLogger.debug("save connectorTask={}", connectorTask);
                    channelByAddress.attr(NettyConfigHelper.connectTaskKey).set(connectorTask);
                } else {
                    terminalLogger.error("build aa too much times comId={}, a-taskNo={}", terminal.getComId(), devicePacketHandleResult.getConnectTaskResultDto().getAtomicTaskNo());
                    devicePacketHandleResult.getConnectTaskResultDto().setExecuteCode(ConnectorTaskResult.ExecuteCode.fail);
                }
            }

        }
        return devicePacketHandleResult;
    }

    private void resetContextAndCleanChannel(String netAddress, TaskContext taskContext, String atomicTaskNo) {
        log.debug("refresh task context and clean channel last context");
        taskContext.refresh(atomicTaskNo);
        terminalChannelService.removeChannelLastContext(netAddress);
    }

    private DevicePacketHandleResult handleDevicePullPacket(Channel channel, TaskContext taskContext, ComEntity terminal, byte[] result, String deviceAddress) {
        GXDLMSClient gxdlmsClient = taskContext.getClient();
        ConnectorTask connectorTask = channel.attr(NettyConfigHelper.connectTaskRegular).get();
        DevicePacketHandleResult handleResult = new DevicePacketHandleResult();
        final Logger terminalLogger = LoggerBuilder.getLogger(logFileNameUtil.getConnectInfoLogFileName(deviceConnectService.getConnectionInfoThreadLocal().get()), this.getClass());
        if (gxdlmsClient == null) {
            log.error("get byte without client: content={}", ByteUtils.byteToHexString(result));
            return handleResult;
        }
        GXReplyData notify = taskContext.getNotify() == null ? new GXReplyData() : taskContext.getNotify();
        GXByteBuffer buffer = new GXByteBuffer();

        String atomicTaskNo = null;
        buffer.set(result);
        if (gxdlmsClient instanceof GXDLMSSecureClient && needEncrypt((GXDLMSSecureClient) gxdlmsClient)) {
            buffer.clear();
            byte[] decryptHLSPacket = new byte[0];
            try {
                decryptHLSPacket = decryptHLSPacket(result, (GXDLMSSecureClient) gxdlmsClient);
            } catch (Exception e) {
                terminalLogger.error("decryption failed, comId={}", terminal.getComId(), e);
                handleResult.setConnectTaskResultDto(new ConnectTaskResultDto(ConnectorTaskResult.ExecuteCode.ExceptionResponse, taskContext.getAtomicTaskNo(), null, "DecryptedFailed", taskContext.getComId(), taskContext.getFlag(), connectorTask.getTicketNumber()));
                return handleResult;
            }
            buffer.set(decryptHLSPacket);
            LoggerBuilder.getLogger(logFileNameUtil.getConnectInfoLogFileName(deviceConnectService.getConnectionInfoThreadLocal().get()), this.getClass()).info("after decrypt: {}", ByteUtils.byteToHexString(decryptHLSPacket));
        }
        terminalLogger.debug("taskContext: comId={},channelComId={},a-taskNo={}", taskContext.getComId(), terminal.getComId(), taskContext.getAtomicTaskNo());
        //增加一步返回ExceptionResponse(D8)时的处理
        String returnMessageXml = null;
        try {
            returnMessageXml = translator.messageToXml(buffer.getData());
            if ("ExceptionResponse".equalsIgnoreCase(getResponseType(returnMessageXml))) {
                terminalLogger.error("aa invalid: a-taskNo={}, message={}", taskContext.getAtomicTaskNo(), returnMessageXml);
                handleResult.setConnectTaskResultDto(new ConnectTaskResultDto(ConnectorTaskResult.ExecuteCode.ExceptionResponse, taskContext.getAtomicTaskNo(), returnMessageXml, getExceptionResponse(returnMessageXml), taskContext.getComId(), taskContext.getFlag(), connectorTask.getTicketNumber()));
                return handleResult;
            }
        } catch (Exception e) {
            terminalLogger.error("buffer to xml failed", e);
            return handleResult;
        }

        byte packetInvokeId = getInvokeIdFromPacket(buffer.getData(), gxdlmsClient);
        terminalLogger.debug("invokeId={},targetInvokeId={}", taskContext.getInvokeId(), packetInvokeId & 0x0F);

        if (!invokeIdVerify(taskContext, packetInvokeId)) {
            terminalLogger.error("invokeId mismatch: get atomicTaskNo in context fail, invokeId={},targetInvokeId={}", taskContext.getInvokeId(), packetInvokeId & 0x0F);
            terminalLogger.debug("result={}", ByteUtils.byteToHexString(buffer.getData()));
//                deviceConnectService.sendDeviceResultMessage(ConnectorTaskResult.ExecuteCode.fail, taskContext.getAtomicTaskNo(), taskContext.getAtomicTaskNo(), "invokeId mismatch");
            return handleResult;
        }
        atomicTaskNo = taskContext.getAtomicTaskNoByInvokeId(packetInvokeId & 0x0F);
        terminalLogger.debug("return packet fetch atomicTaskNo={}, taskContext={}", atomicTaskNo, JSONUtil.toJsonStr(taskContext));
        if (StringUtils.isEmpty(atomicTaskNo)) {
            terminalLogger.error("failed get atomicTaskNo in context");
            return handleResult;
        }
        // 把上一帧回的frameId存起来给下一帧的request为基础自增
        short lastFrameType = Cosem46ByteUtil.getFrameType(gxdlmsClient, result);
        if (lastFrameType != -1) {
            //HDLC的必须计算出senderFrame receiverFrame
            taskContext.setLastFrameId(lastFrameType);
            gxdlmsClient.getHdlcSettings().setSenderFrame(Cosem46ByteUtil.decreaseReceiverSequence(lastFrameType));
            gxdlmsClient.getHdlcSettings().setReceiverFrame(Cosem46ByteUtil.decreaseSendSequence(Cosem46ByteUtil.decreaseReceiverSequence(lastFrameType)));
        }
        try {
            if (CollUtil.isNotEmpty(taskContext.getSendQueueByAtomicTaskNo(atomicTaskNo))) {
                FramePacket framePacket = taskContext.getSendQueueByAtomicTaskNo(atomicTaskNo).poll();
                taskContext.getInvokeId().set(framePacket.getInvokeId());
                taskContext.addInvokeIdAndAtomicTaskNo(framePacket.getInvokeId(), framePacket.getAtomicTaskNo());
                return handleResult.setResponse(framePacket.getData());
            } else {
                gxdlmsClient.getData(buffer, notify);
            }
        } catch (InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException | InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException | IllegalArgumentException e) {
            terminalLogger.error("decode packet fail {}", result == null ? "no byte" : ByteUtils.byteToHexString(result), e);
            handleResult.setConnectTaskResultDto(new ConnectTaskResultDto(ConnectorTaskResult.ExecuteCode.fail, atomicTaskNo, taskContext.getAtomicTaskNo(), e.getMessage(), terminal.getComId(), taskContext.getFlag(), connectorTask.getTicketNumber()));
            return handleResult;
        } catch (GXDLMSExceptionResponse exception) {
            terminalLogger.error("gurux dlms decode fail  ", exception);
            ConnectorTaskResult.ExecuteCode executeCode = ConnectorTaskResult.ExecuteCode.fail;
            if (exception.getMessage().indexOf("ServiceError") > 0) {
                terminalLogger.info("Service error: {}", exception.getMessage());
                executeCode = ConnectorTaskResult.ExecuteCode.ExceptionResponse;
            }
            handleResult.setConnectTaskResultDto(new ConnectTaskResultDto(executeCode, atomicTaskNo, taskContext.getAtomicTaskNo(), exception.getMessage(), terminal.getComId(), taskContext.getFlag(), connectorTask.getTicketNumber()));
            return handleResult;
        } catch (UDISDeviceConException e) {
            handleResult.setConnectTaskResultDto(new ConnectTaskResultDto(ConnectorTaskResult.ExecuteCode.fail, atomicTaskNo, taskContext.getAtomicTaskNo(), e.getMessage(), terminal.getComId(), taskContext.getFlag(), connectorTask.getTicketNumber()));
            return handleResult;
        }

        if (DeviceType.GPRS.getId().intValue() != terminal.getComDeviceType() && taskContext.getMeterId() != null) {
            //Meter under terminal
            assetManagementRemoteService.getMeterById(taskContext.getMeterId());
        }
        terminalLogger.debug("notify {} ", notify.isMoreData());
        if (notify.isMoreData()) {
            redisUtils.set(RedisKeys.getTaskLastAliveTime(connectorTask.getTaskNo()), Convert.toStr(SystemClock.now()), 600);
            byte[] nextBlockRequest = gxdlmsClient.receiverReady(notify);
            taskContext.setNotify(notify);
            if (gxdlmsClient instanceof GXDLMSSecureClient && needEncrypt((GXDLMSSecureClient) gxdlmsClient)) {
                try {
                    ((GXDLMSSecureClient) gxdlmsClient).getCiphering().setInvocationCounter(((GXDLMSSecureClient) gxdlmsClient).getCiphering().getInvocationCounter() + 1);
                } catch (Exception e) {
                    terminalLogger.error("encrypt packet fail {}", e);
                }
            }
            byte[] wrappedNextRequest = wrapPrefixToPacket(nextBlockRequest, terminal, deviceAddress);
            taskService.refreshAtomicTaskNo(terminal.getComId(), taskContext.getTaskNo(), taskContext.getAtomicTaskNo(), wrappedNextRequest);
            return handleResult.setResponse(wrappedNextRequest);
        }
        //refresh task context
        GXByteBuffer byteBuffer = notify.getData();
//        log.info(ByteUtils.byteToHexString(byteBuffer.getData()));
        String resultString = null;
        try {
            resultString = translator.dataToXml(byteBuffer);
        } catch (Exception e) {
            terminalLogger.error("data to xml fail {}", byteBuffer != null ? ByteUtils.byteToHexString(byteBuffer.getData()) : null, e);
        }
        terminalLogger.debug("translated xml: {}", resultString);
        //无法翻译出pdu内的data时result为""
        if (StringUtils.isEmpty(resultString) || GURUX_NONE_DATA_STRING.equalsIgnoreCase(resultString.trim())) {
            //翻译成message的xml
            try {
                translator.setComments(false);
                resultString = translator.messageToXml(buffer.getData());
            } catch (Exception e) {
                terminalLogger.error("message to xml fail ", e);
            }

            if (StringUtils.isEmpty(resultString)) {
                handleResult.setConnectTaskResultDto(new ConnectTaskResultDto(ConnectorTaskResult.ExecuteCode.fail, atomicTaskNo, null, "decode fail", terminal.getComId(), taskContext.getFlag(), connectorTask.getTicketNumber()));
                return handleResult;
            }
            terminalLogger.debug("result ={}", resultString);
            String dataAccessCode = getDataAccessCode(resultString);
            terminalLogger.info("get accessCode={}", dataAccessCode);
            if ("success".equalsIgnoreCase(dataAccessCode) || "TemporaryFailure".equalsIgnoreCase(dataAccessCode)) {
                handleResult.setConnectTaskResultDto(new ConnectTaskResultDto(ConnectorTaskResult.ExecuteCode.success, atomicTaskNo, null, dataAccessCode, terminal.getComId(), taskContext.getFlag(), connectorTask.getTicketNumber()));
                return handleResult;
            } else {
                handleResult.setConnectTaskResultDto(new ConnectTaskResultDto(ConnectorTaskResult.ExecuteCode.fail, atomicTaskNo, null, dataAccessCode, terminal.getComId(), taskContext.getFlag(), connectorTask.getTicketNumber()));
                return handleResult;
            }
        }
        //翻译得到Cosem xml
        else {
            resultString = resultString.replace("\r", "").replace("\n", "");
        }
        handleResult.setConnectTaskResultDto(new ConnectTaskResultDto(ConnectorTaskResult.ExecuteCode.success, atomicTaskNo, resultString, null, terminal.getComId(), taskContext.getFlag(), connectorTask.getTicketNumber()));
        return handleResult;
    }

    private byte getInvokeIdFromPacket(byte[] packet, GXDLMSClient gxdlmsClient) {
        if (gxdlmsClient.getInterfaceType().equals(InterfaceType.HDLC)) {
            return (byte) Cosem46ByteUtil.getInvokeId(gxdlmsClient, packet);
        }
        return packet[10];
    }

    @Override
    public Integer invokeIdAdd(String logFileName, byte[] packetByte, String netAddress, TaskContext context, String taskNo) throws NoSuchPaddingException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException {
        GXDLMSTranslator translator = new GXDLMSTranslator(TranslatorOutputType.SIMPLE_XML);
        byte[] pdu = translator.getPdu(packetByte);
        Integer index = Bytes.indexOf(packetByte, pdu);
        log.debug("invokeId={},contextHAsh={}", context.getInvokeId().get(), context.hashCode());
        Integer nextInvokeId = context.getInvokeId().incrementAndGet() % 16;
        LoggerBuilder.getLogger(logFileName, this.getClass()).info("addInvokeId:{},taskNo:{}", nextInvokeId, taskNo);
        byte invokeId = (byte) (nextInvokeId.shortValue() | 0x40);
        try {
            context.getClient().setInvokeID(nextInvokeId);
        } catch (Exception e) {
            log.error("invokeid {}", nextInvokeId, e);
        }
        terminalChannelService.putNetAddressTaskContext(netAddress, context);
        System.arraycopy(new byte[]{invokeId}, 0, packetByte, index + 2, 1);
        return nextInvokeId;
    }

    private boolean invokeIdVerify(TaskContext taskContext, byte packetInvokeId) {
        String atomicTaskNo = taskContext.getAtomicTaskNoByInvokeId(packetInvokeId & 0x0F);
        if (StringUtils.isEmpty(atomicTaskNo)) {
            return false;
        }

        if (taskContext.getInvokeId().get() != (packetInvokeId & 0x0f)) {
            return false;
        }
        return true;
    }

    @Override
    public boolean isAssociationRelease(byte[] result, String netAddress, String responseType, ProtocolType protocolType) {
        String cosemMessage = this.cosemXml.get();
        if ("ExceptionResponse".equals(responseType)) {
            Attribute<ConnectorTask> attribute = NettyConfigHelper.getChannelByAddress(netAddress).attr(NettyConfigHelper.connectTaskKey);
            if (attribute.get() != null) {
                return true;
            }
        }
        if ("ReleaseResponse".equals(responseType)) {
            return true;
        }
        if ("GatewayResponse".equalsIgnoreCase(responseType) && cosemMessage.contains("ReleaseResponse")) {
            return true;
        }
        // 46子母表releaseResponse可能为以下两种内容
        if (ProtocolType.DLMS_COSEM_46_Protocol.equals(protocolType) && isHDLCRelease(cosemMessage)) {
            return true;
        }
        return false;
    }

    private boolean isHDLCRelease(String resultXml) {
        Pattern pattern = Pattern.compile("<Ua>\\r\\n</Ua>");
        Matcher matcher = pattern.matcher(resultXml);
        if (matcher.find()) {
            return true;
        }
        Pattern pattern2 = Pattern.compile("<DisconnectMode>");
        Matcher matcher2 = pattern2.matcher(resultXml);
        if (matcher2.find()) {
            return true;
        }
        return false;
    }

    @Override
    public byte[] wrapPrefixToPacket(byte[] packetBytes, ComEntity terminal, String deviceAddress) {
        if (terminal.getComDeviceType().intValue() == DeviceType.DCU.getId()) {
            if (deviceAddress == null || deviceAddress.equalsIgnoreCase("-1")) {
                return packetBytes;
            }

            GXDLMSTranslator translator = new GXDLMSTranslator(TranslatorOutputType.SIMPLE_XML);
            try {
                byte[] pdu = translator.getPdu(packetBytes);
                int wrapIndex = Bytes.indexOf(packetBytes, pdu);
                byte[] header = new byte[wrapIndex];
                System.arraycopy(packetBytes, 0, header, 0, wrapIndex);
                byte[] pduLength = new byte[2];
                System.arraycopy(header, wrapIndex - 2, pduLength, 0, 2);
                Integer length = Integer.valueOf(ByteNumberUtils.byte2ToShort(pduLength));
                byte[] meterDLMSGatewayHeader = getDLMSGatewayPrefix(deviceAddress);
                length += meterDLMSGatewayHeader.length;
                pduLength = ByteNumberUtils.shortToByte2(length.shortValue());
                //copy back new pdu length
                System.arraycopy(pduLength, 0, header, wrapIndex - 2, 2);
                return Bytes.concat(header, meterDLMSGatewayHeader, pdu);
            } catch (InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException | InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) {
            }
        }
        return packetBytes;
    }

    private byte[] getDLMSGatewayPrefix(String meterNo) {
        byte[] prefixHeaderAndNetworkID = new byte[]{(byte) 0xE6, 0x00};
        byte[] length = new byte[]{(byte) meterNo.length()};
        byte[] deviceAddress = meterNo.getBytes();
        return Bytes.concat(prefixHeaderAndNetworkID, length, deviceAddress);
    }

    @Override
    public UnwrappedPacket unwrapPrefixPacket(byte[] packet) {
        UnwrappedPacket unwrappedPacket = new UnwrappedPacket();
        unwrappedPacket.setPacket(packet);
        try {
            byte[] pdu = translator.getPdu(packet);
            if (pdu == null || pdu.length == 0) {
                return unwrappedPacket;
            }
            if (pdu[0] == (byte) 0xe7 || pdu[0] == (byte) 0xe6) {
                int wrapIndex = Bytes.indexOf(packet, pdu);
                byte[] prefixPduLen = new byte[2];
                System.arraycopy(packet, wrapIndex - 2, prefixPduLen, 0, 2);

                int totalPduLength = ByteNumberUtils.byte2ToShort(prefixPduLen);

                //获取第3个字节的设备地址长度
                byte prefixLen = pdu[2];
                byte[] deviceAddress = new byte[prefixLen];
                System.arraycopy(pdu, 3, deviceAddress, 0, prefixLen);
                String meterNo = new String(ByteUtils.getChars(deviceAddress));
                unwrappedPacket.setDeviceAddress(meterNo);
                byte[] wrapper = new byte[wrapIndex];
                System.arraycopy(packet, 0, wrapper, 0, wrapIndex);

                int totalPrefixLen = 3 + prefixLen;
                int devicePduLength = totalPduLength - totalPrefixLen; // e7 00 0x {device info} {device pdu}
                byte[] devicePdu = new byte[devicePduLength];
                System.arraycopy(packet, wrapIndex + totalPrefixLen, devicePdu, 0, devicePduLength);

                byte[] wrapLength = ByteNumberUtils.shortToByte2((short) devicePduLength);
                System.arraycopy(wrapLength, 0, wrapper, wrapper.length - 2, 2);
                unwrappedPacket.setPacket(Bytes.concat(wrapper, devicePdu));
            }
        } catch (Exception e) {
            log.error("unwrap fail , packet {}", ByteUtils.byteToHexString(packet));
            return null;
        }

        return unwrappedPacket;
    }


    public TaskContext buildTaskContext(ComEntity terminal, String netAddress, ConnectorTask connectorTask, AssociationState aaState) throws UnsupportedEncodingException {
        TaskContext taskContext = this.initTaskContext(terminal, aaState);
        terminalChannelService.putNetAddressTaskContext(netAddress, taskContext);
        return taskContext;
    }

    private TaskContext initTaskContext(ComEntity terminal, AssociationState aaState) {
        Preconditions.checkNotNull(aaState);
        TaskContext taskContext = new TaskContext();
        taskContext.setComId(terminal.getComId());
        taskContext.setInvokeId(new AtomicInteger(0));
        taskContext.setDeviceAddress(StrUtil.equals(aaState.getDeviceAddress(), "-1") ? null : aaState.getDeviceAddress());
        taskContext.setClient(aaState.getClient());
//        DeviceSAPandKeys deviceConnectKeys;
//        if (meter == null) {
//            deviceConnectKeys = assetManagementRemoteService.getTerminalCommunicationKey(terminal.getComId(), connectorTask.getRoleId());
//        } else {
//            deviceConnectKeys = assetManagementRemoteService.getMeterCommunicationKey(meter.getMeterId(), connectorTask.getRoleId());
//        }
//        taskContext.setIcSubDataItemId(deviceConnectKeys.getIcSubDataItemId());
//        taskContext.setRoleId(connectorTask.getRoleId());
//        taskContext.setClient(getDLMSClient(terminal, meter, deviceConnectKeys));
        return taskContext;
    }

    @Override
    public PacketType getPacketType(byte[] result, ComEntity terminal, ComConfigDMO comConfigDMO) throws NoSuchPaddingException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException {
        PacketType packetType = new PacketType();
        if (comConfigDMO == null
                || comConfigDMO.getProtocolId() == null
                || (comConfigDMO.getProtocolId() == ProtocolType.DLMS_Gateway_Protocol.getProtocolId()
                || ProtocolType.isDlmsMeter(comConfigDMO.getProtocolId().intValue()))) {
            GXDLMSTranslator translator = new GXDLMSTranslator(TranslatorOutputType.SIMPLE_XML);
            UnwrappedPacket unwrappedPacket = this.unwrapPrefixPacket(result);
            if (translator.getPdu(result).length == 0) {
                // 46 release报文getPdu解析不出来
                packetType.setType(PacketType.Type.Data);
                return packetType;
            }
            String resultXml = translator.pduToXml(translator.getPdu(result));
            CosemData cosemData = CosemXmlUtils.translateCosemXml(resultXml);
            if (cosemData != null) {
                packetType.setData(resultXml);
                packetType.setDeviceAddress(unwrappedPacket.getDeviceAddress());

                if ("DataNotification".equalsIgnoreCase(cosemData.getType())
                        || (!cosemData.getChildAt(2).getIsNull() && "DataNotification".equalsIgnoreCase(cosemData.getChildAt(2).getType()))
                ) {
                    packetType.setType(PacketType.Type.DataNotification);
                    return packetType;
                }
                if ("EventNotificationRequest".equalsIgnoreCase(cosemData.getType()) || (!cosemData.getChildAt(2).getIsNull() && "EventNotificationRequest".equalsIgnoreCase(cosemData.getChildAt(2).getType()))) {
                    packetType.setType(PacketType.Type.EventNotificationRequest);
                    return packetType;
                }
            }
            packetType.setType(PacketType.Type.Data);
            return packetType;
        } else {
            log.error("protocol not support");
        }
        return null;
    }

    @Override
    public byte[] encryptHLSPacket(byte[] wrappedPdu, GXDLMSSecureClient client) throws NoSuchPaddingException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException {
        GXDLMSTranslator t = new GXDLMSTranslator(TranslatorOutputType.SIMPLE_XML);
        byte[] pdu = t.getPdu(wrappedPdu);
        GXCiphering gxCiphering = client.getCiphering();
        Integer tag = getHLSEncryptTag(pdu[0] & 0xff);
        LoggerBuilder.getLogger(logFileNameUtil.getConnectInfoLogFileName(deviceConnectService.getConnectionInfoThreadLocal().get()), this.getClass()).info("before encrypt: {}", ByteUtils.byteToHexString(wrappedPdu));
//        LoggerBuilder.getLogger(deviceConnectService.getComEntityThreadLocal().get().getModuleSn(), this.getClass()).info("{} {} {} {} {} {}", tag, gxCiphering.getSecurity(), gxCiphering.getInvocationCounter()
//                , ByteUtils.byteToHexString(gxCiphering.getSystemTitle())
//                , ByteUtils.byteToHexString(gxCiphering.getBlockCipherKey())
//                , ByteUtils.byteToHexString(gxCiphering.getAuthenticationKey()));
        AesGcmParameter p = new AesGcmParameter(tag, gxCiphering.getSecurity(), SecuritySuite.SUITE_0, gxCiphering.getInvocationCounter()
                , gxCiphering.getSystemTitle()
                , gxCiphering.getBlockCipherKey()
                , gxCiphering.getAuthenticationKey());
        byte[] pduRecreate = GXCiphering.encrypt(p, pdu);
        return recalculateCosemPduLength(wrappedPdu, pdu, pduRecreate);
    }

    @Override
    public byte[] decryptHLSPacket(byte[] wrappedPdu, GXDLMSSecureClient client) throws NoSuchPaddingException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException {
        LoggerBuilder.getLogger(logFileNameUtil.getConnectInfoLogFileName(deviceConnectService.getConnectionInfoThreadLocal().get()), this.getClass()).debug(ByteUtils.byteToHexString(wrappedPdu));
        GXCiphering gxCiphering = client.getCiphering();
        GXDLMSTranslator t = new GXDLMSTranslator(TranslatorOutputType.SIMPLE_XML);
        byte[] gloPdu = t.getPdu(wrappedPdu);
//        String s = ByteUtils.byteToHexString(gloPdu);
//        LoggerBuilder.getLogger(deviceConnectService.getConnectionInfoThreadLocal().get().getModuleSn(), this.getClass()).info("decrypt tag: {}", tag);
        if (tagNotInDecryption(gloPdu[0] & 0xff)) {
            LoggerBuilder.getLogger(logFileNameUtil.getConnectInfoLogFileName(deviceConnectService.getConnectionInfoThreadLocal().get()), this.getClass()).info("tag not decryption");
            return wrappedPdu;
        }

//        LoggerBuilder.getLogger(deviceConnectService.getConnectionInfoThreadLocal().get().getModuleSn(), this.getClass()).info("server {}, source {}"
//                , new String(client.getServerSystemTitle()), new String(client.getSourceSystemTitle()));

        GXDLMSServerBase gxdlmsServerBase = new GXDLMSServerBase(null, true, InterfaceType.PDU);
        gxdlmsServerBase.getSettings().setServer(false);
        AesGcmParameter p = new AesGcmParameter(gxdlmsServerBase.getSettings()
                , client.getSourceSystemTitle()
                , gxCiphering.getBlockCipherKey()
                , gxCiphering.getAuthenticationKey());
        LoggerBuilder.getLogger(logFileNameUtil.getConnectInfoLogFileName(deviceConnectService.getConnectionInfoThreadLocal().get()), this.getClass()).debug(ByteUtils.byteToHexString(gloPdu));
        GXByteBuffer gxByteBuffer = new GXByteBuffer();
        gxByteBuffer.set(gloPdu);

        byte[] pduRecreate = GXCiphering.decrypt(gxCiphering, p, gxByteBuffer);
        return recalculateCosemPduLength(wrappedPdu, gloPdu, pduRecreate);

    }

    private boolean tagNotInDecryption(int tag) {
        for (int i : glo_tag) {
            if (tag == i) {
                return false;
            }
        }

        return true;
    }

    private byte[] recalculateCosemPduLength(byte[] wrappedPdu, byte[] pdu, byte[] pduRecreate) {
        int wrapIndex = Bytes.indexOf(wrappedPdu, pdu);
        byte[] wrapLength = ByteNumberUtils.shortToByte2((short) pduRecreate.length);
        byte[] bytesClone = Arrays.copyOfRange(wrappedPdu, 0, wrappedPdu.length - 1);
        System.arraycopy(wrapLength, 0, bytesClone, wrapIndex - 2, 2);
        byte[] wrapper = Arrays.copyOfRange(bytesClone, 0, wrapIndex);
        return Bytes.concat(wrapper, pduRecreate);
    }


    private Integer getHLSEncryptTag(int b) {
        if (b < 25) {
            return b + 32;
        } else {
            return b + 8;
        }
    }

    @Override
    public boolean needEncrypt(GXDLMSSecureClient client) {
        switch (client.getCiphering().getSecurity()) {
            case NONE:
                break;
            case AUTHENTICATION:
                break;
            case ENCRYPTION:
            case AUTHENTICATION_ENCRYPTION:
                return true;
        }
        return false;
    }

    public DLMSConfigDMO getDLMSConfigDMO(ComEntity terminal, Meter meter) {
        if (meter == null) {
            return assetService.getComConfigById(terminal.getComId());
        } else {
            return assetService.getMeterConfig(meter.getMeterId());
        }
    }

    @Override
    public TaskContext initializeTaskContext(TaskContext context, ConnectorTask connectorTask, AssociationState aaState) {
        context.setAtomicTaskNo(connectorTask.getAtomicTaskNo());
        context.setTaskNo(connectorTask.getTaskNo());
        context.setReturnPacket(connectorTask.getReturnPacket());
        context.setMeterId(connectorTask.getMeterId());
        context.setClient(aaState == null ? null : aaState.getClient());
        context.refresh(connectorTask.getAtomicTaskNo());
        context.setFlag(connectorTask.getFlag());
        assert aaState != null;
        if (!StrUtil.equals(aaState.getDeviceAddress(), "-1")) {
            context.setDeviceAddress(aaState.getDeviceAddress());
        }
        return context;
    }

    @Override
    public boolean isDLMSGatewayTransparentFailedPkg(byte[] result) {
        if (result.length < 9) {
            return false;
        }
        int mark = result[8] & 0xFF;
        if (mark != 0xE6 && mark != 0xE7) {
            return false;
        }
        int cosemPkgLen = result[7];
        int deviceLen = result[10];

        if (deviceLen + 3 == cosemPkgLen) {
            return true;
        }

        return false;
    }

    @Override
    public String setCosemMessage(byte[] result) {
        try {
            String cosemXml = translator.messageToXml(result);
            String pduXml = translator.pduToXml(translator.getPdu(result));
            this.cosemXml.set(cosemXml);
            this.pduXml.set(pduXml);
            return cosemXml;
        } catch (Exception e) {
            log.info("translateCosmePduFailed, {}", ByteUtils.byteToHexString(result), e);
        }
        return null;
    }

    @Override
    public void handleUntranslatablePacket(byte[] result, Channel channel) {
        ConnectorTask connectorTask = channel.attr(NettyConfigHelper.connectTaskRegular).get();
        ComEntity com = channel.attr(NettyConfigHelper.comEntityAttrKey).get();
        if ((connectorTask.getFlag() & 2) > 0) {
            deviceConnectService.sendDeviceResultMessage(new ConnectTaskResultDto(ConnectorTaskResult.ExecuteCode.success, connectorTask.getAtomicTaskNo(), String.format("<Raw Value=\"%s\"/>", HexUtil.encodeHex(result)), null, com.getComId(), connectorTask.getFlag(), connectorTask.getTicketNumber()), com);
            return;
        }
        LoggerBuilder.getLogger(logFileNameUtil.getLogFileName(com), this.getClass()).error("untranslatablePacketIntoCosemChannel:{}", ByteUtils.byteToHexString(result));
    }

    @Override
    public boolean resendConnectorTask(ComEntity comEntity, Channel channel) {
        Attribute<ConnectorTask> attribute = channel.attr(NettyConfigHelper.connectTaskKey);
        ConnectorTask task = attribute.getAndSet(null);
        //HLSAssociationBuilder中也有ReleaseResponse返回，此处判断如果没有缓存的connectorTask则进入到SyncFuture对象，供HLSAssociationBuilder中的同步release使用
        if (task != null) {
            LoggerBuilder.getLogger(logFileNameUtil.getLogFileName(comEntity), this.getClass()).info("execute task after release association taskNo={}, atomicTaskNo={}", task.getTaskNo(), task.getAtomicTaskNo());
            connectorTaskPool.execute(() -> {
                deviceConnectService.sendToDevice(task);
            });
            return true;
        }
        return false;
    }

    @Override
    public Boolean acceptHandleNotifications(String responseType, Channel channel, byte[] cosemApdu) {
        String cosemXml = this.cosemXml.get();
        Boolean isNotification = isCosemPduNotification(responseType, cosemXml);
        if (isNotification) {
            LoggerBuilder.getLogger(logFileNameUtil.getLogFileName(channel.attr(NettyConfigHelper.comEntityAttrKey).get()), this.getClass()).info("^^^^^^^^^^^^^^ data notification ^^^^^^^^^^^^^");
            String pduXml = this.getPduXml();
            UnwrappedPacket unwrappedPacket = this.unwrapPrefixPacket(cosemApdu);
//            Meter meter = StringUtils.isEmpty(unwrappedPacket.getDeviceAddress()) ? null : assetManagementRemoteService.getMeterByMfgSn(unwrappedPacket.getDeviceAddress());
//            deviceStatusService.updateDeviceStatus(terminal, meter);
//            deviceStatusService.updateComEntityStatus(terminal, ComEntity.CommunicationStatus.ONLINE.getCode());
//            deviceStatusService.updatePlcMeterStatus(meter, PlcMeterStatus.Status.Online.getCode());
            deviceConnectService.sendNotificationToUTE(pduXml, channel, unwrappedPacket.getDeviceAddress());
        }
        return isNotification;
    }


    public String getCosemXml() {
        return this.cosemXml.get();
    }

    public String getPduXml() {
        return this.pduXml.get();
    }

    private Boolean isCosemPduNotification(String responseType, String cosemXml) {
        if ("EventNotificationRequest".equals(responseType) || "DataNotification".equals(responseType)) {
            return true;
        }
        if (StrUtil.isEmpty(cosemXml)) {
            return false;
        }
        if (cosemXml.contains("<EventNotificationRequest>") || cosemXml.contains("<DataNotification>")) {
            return true;
        }
        return false;
    }

    /**
     * dcu channel下发频段及回复的报文都是68开头,特殊处理
     * 表计at指令回复的报文为OD 0A开头或者2B开头
     * @param result
     * @return
     */
    @Override
    public boolean isChannelSpecialPkg(byte[] result) {
        if (result[0] == 0x68 || result[0] == 0x2B || (result[0] == 0x0D && result[1] == 0x0A)) {
            return true;
        }
        return false;
    }


    @Override
    public byte[] getNotificationResponse(String data) {
        CosemData cosemData = CosemXmlUtils.translateCosemXml(data);
        if (cosemData.getType().equals("GatewayResponse")) {
            //电表推送事件使用GatewayResponse(E7)
            cosemData = getCosemDataAtDataNotificationPosition(cosemData);
        }
        //DLMS Gateway推送仅用DataNotification
        if (!cosemData.getType().equals("DataNotification")) {
            return null;
        }
        //获取InvokeId
        String invokeId = cosemData.getChildAt(0).getValue();
        byte[] invokeIdBytes = ByteUtils.hexStringToByte(invokeId);
        //获取数据的Structure对象
        CosemData dataValueStructureObj = cosemData.getChildAt(2).getChildAt(0).getChildAt(0);
        //Dlms Gateway的上报中，第一个为时间，第二个是EventId
        CosemData dateTime = dataValueStructureObj.getChildAt(0);
        if (!CosemUtils.isDateTimeString(dateTime.getValue())) {
            return null;
        }
        GXByteBuffer byteBuffer = new GXByteBuffer();
        GXByteBufferUtils.setOctStrDateTime(byteBuffer, SystemClock.now() / 1000);
        byte[] dateTimeByte = new byte[13];
        System.arraycopy(byteBuffer.getData(), 1, dateTimeByte, 0, 13);
        log.info(ByteUtils.byteToHexString(dateTimeByte));
        //0c 07 e4 06 0a 03 17 26 0e ff 80 00 00
        CosemData eventCode = dataValueStructureObj.getChildAt(1);
        byte[] eventId = ByteNumberUtils.shortToByte2(Short.parseShort(eventCode.getValue(), 16));
        return getDlmsGatewayConfirm(invokeIdBytes, dateTimeByte, eventId);
    }


    private byte[] getDlmsGatewayConfirm(byte[] invokeIdBytes, byte[] dateTimeByte, byte[] eventId) {
        Integer length = invokeIdBytes.length + dateTimeByte.length + eventId.length + 2;
        byte[] bytes = ByteNumberUtils.shortToByte2(length.shortValue());
        byte[] concat = Bytes.concat(wrapper, bytes, new byte[]{0x0f}, invokeIdBytes, dateTimeByte, new byte[]{0x12}, eventId);
        return concat;
    }

    private CosemData getCosemDataAtDataNotificationPosition(CosemData cosemData) {
        return cosemData.getChildAt(2);
    }

    /*public static void main(String[] args) throws NoSuchPaddingException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException {
        String d = "00 01 00 01 00 01 02 E6 C4 02 C6 01 00 00 00 03 00 82 02 DA 15 19 F4 06 00 17 DA A4 06 00 03 71 A4 06 00 2D 25 D0 06 00 15 8F 88 06 00 21 EA 08 06 00 08 95 44 06 00 0E E4 80 06 00 21 7F C8 06 00 04 99 58 06 00 10 F7 C0 06 00 08 99 F4 06 00 05 43 A8 06 00 02 39 24 06 00 14 98 A8 06 00 02 72 F4 06 00 1A CD B8 06 00 1A 7E 6C 06 00 24 AD D8 06 00 14 CE 90 02 1E 09 0C 07 E5 03 0D 06 09 00 00 00 80 00 FF 11 7A 06 00 27 BE 38 06 00 01 33 6C 06 00 0B CC B4 06 00 03 44 B8 06 00 09 87 D8 06 00 16 57 EC 06 00 2B 4A 08 06 00 0E C5 40 06 00 22 F2 7C 06 00 20 D5 DC 06 00 02 13 40 06 00 10 29 80 06 00 01 EB CC 06 00 05 4A B0 06 00 04 21 08 06 00 21 16 B4 06 00 25 D5 8C 06 00 13 99 30 06 00 30 AD 54 06 00 1A 32 40 06 00 0A C6 34 06 00 2E 6B FC 06 00 17 F1 B0 06 00 05 26 C0 06 00 2B B9 C0 06 00 23 E1 F0 06 00 2F 5E 90 06 00 27 3C EC 02 1E 09 0C 07 E5 03 0D 06 09 0F 00 00 80 00 FF 11 98 06 00 19 77 EC 06 00 30 1C CC 06 00 29 1C 9C 06 00 21 4B D4 06 00 06 41 F4 06 00 18 A4 34 06 00 19 3E 80 06 00 1C 81 A8 06 00 0B 7E 94 06 00 23 40 9C 06 00 11 A7 88 06 00 12 DF 40 06 00 10 9A 64 06 00 04 4C 00 06 00 12 75 C8 06 00 09 8A 94 06 00 0E AA B0 06 00 01 F2 70 06 00 2B D2 C0 06 00 2E F6 A8 06 00 06 7D B8 06 00 1E 50 8C 06 00 30 DC FC 06 00 06 6E E0 06 00 13 3F BC 06 00 17 B8 A8 06 00 08 14 C0 06 00 07 0D 78 02 1E 09 0C 07 E5 03 0D 06 09 1E 00 00 80 00 FF 11 36 06 00 2D F2 80 06 00 04 DD 50 06 00 31 76 E4 06 00 2F DD 84 06 00 2A 43 EC 06 00 06 A4 64 06 00 2E A3 D8 06 00 18 B2 44 06 00 1D 11 CC 06 00 1B 3E 9C 06 00 2B 0C 50 06 00 07 03 50 06 00 1C EB 20 06 00 03 08 2C 06 00 2A 86 B8 06 00 13 90 FC 06 00 21 CA 00 06 00 22 5B 50 06 00 28 22 9C 06 00 0D CF 8C 06 00 04 63 70 06 00 12 EA 30 06 00 1C 5A 98 06 00 1E 99 FC 06 00 26 49 2C 06 00 1C 90 E4 06 00 16 E2 34 06 00 04 03 BC 02 1E 09 0C 07 E5 03 0D 06 09 2D 00 00 80 00 FF 11 26 06 00 06 28 90 06 00 1C 66 50 06 00 2D 54 4C 06 00 30 EC 9C 06 00 21 F3 04 06 00 27 91 4C 06 00 15 44 EC 06 00 0D 94 90 06 00 00 29 04 06 00 2F 9B 80 06 00 0F 84 44 06 00 0A 98 80 06 00 0F 3D 90 06 00 2F BF D4 06 00 25 9E DC 06 00 00 9D 6C 06 00 04 69 4C 06 00 11 E1 58 06 00 12 4D 8C 06 00 17 53 E0 06 00 1C 06 38 06 00 0D AF E8 06 00 05 46 64 06 00 10 4B E0 06 00 06 53 24 06 00 00 9F 60 06 00 30 95 80 06 00 0F 22 38";
        byte packetInvokeId = 48;
        byte sendedByte = (byte) (2 | 0x40);
        System.out.println((packetInvokeId == sendedByte));

    }*/
}
